import Watcher from './Watcher'

export default class Compile {
  /**
   * @description 渲染模板
   * @param {String} el Selector
   * @param {Object} vue Instance of Vue
   */
  constructor (el, vue) {
    // 挂载点
    this.$el = document.querySelector(el)
    // vue实例
    this.$vue = vue

    if (this.$el) {
      // 节点变为fragment
      // 精简版本，Vue源码用的是AST，或者 mustache 里的 tokens
      let $fragment = this.node2Fragment(this.$el)
      // 编译这个fragment，包括模板，指令之类的
      this.compile($fragment)
      // 加入到 DOM 当中
      this.$el.appendChild($fragment)
    }
  }
  /**
   * @param {Element} elm 
   */
  node2Fragment (elm) {
    let fragment = document.createDocumentFragment()
    let child
    // 当 child append之后，elm 当中的这个DOM就删除了
    // 所以每一次的 elm.firstChild 都不同，直到 elm 的children为空 
    while (child = elm.firstChild) {
      fragment.appendChild(child)
    }
    return fragment
  }
  /**
   * @description 类似于中转站
   * @param {Element} frag 
   */
  compile (frag) {
    // Vue源码在这里使用了虚拟节点和diff
    let childNodes = frag.childNodes

    let self = this

    let reg = /\{\{(.+)\}\}/

    childNodes.forEach(node => {
      if (node.nodeType == 1) {
        // element节点
        self.compileElement(node)
      } else if (node.nodeType == 3 && reg.test(node.textContent)) {
        // 要从 vue 实例当中拿到 mustache 括号里面的值
        self.compileText(node, node.textContent)
      }
    })
  }

  /**
   * @description 编译元素节点，这里包括了指令v-for之类的
   * @param {Element} node 
   */
  compileElement (node) {
    // 返回一个类数组，储存节点身上的 attribute
    // 比如 class，会作为一个对象储存，这里class有一个 value 就是class的值
    // 很像虚拟节点
    let nodeAttrs = [...node.attributes]
    const self = this
    nodeAttrs.forEach(attr => {
      let attrName = attr.name
      let value = attr.value
      // 指令
      let dir = attrName.includes('v-') ? attrName.substring(2) : undefined

      if (dir == 'model') {
        // value: 指令内容，这里其实就是实例对象里的数据
        // 这里有性能问题，老师显然没发现
        /**
         *  
          new Watcher(self.$vue, value, v => node.value = v)
          let v = self.getValInVueInstance(self.$vue, value)
          // v-model绑定节点的value，实现数据到视图
          node.value = v
          // 视图到数据的绑定
          node.addEventListener('input', e => {
            let inputval = e.target.value
            self.setValInVueInstance(this.$vue, value, inputval)
            // 下面这一句老师加了，我觉得可以不加
            // v = ininputval
          })
         * 
        */
      } else if (dir == 'for') {
        // console.log('v-for', value)
      } else if (dir == 'if') {
        // console.log('v-if', value)
      } else {
        // DOM 的 attribute
      }
    })
  }

  /**
   * @param {Element} node 
   * @param {String} text 
   */
  compileText (node, text) {
    // text 节点
    let reg = /\{\{(.+)\}\}/
    // 首先拿到模板里面的路径
    // 显然一个文字节点只允许一个双大括号，阉割版的没办法
    let templatestr = text.match(reg)[1]
    // 根据这个路径解析出实例data里的值
    let v = this.getValInVueInstance(this.$vue, templatestr)
    
    // console.log('拿到模板字符', templatestr, '对应的值是：', v)
    node.textContent = text.replace(reg, v)

    // 监听，可以实现视图数据双向更新
    // 这里必须是 fragment 上树之后才能看出来
    new Watcher(this.$vue, templatestr, (cur, old) => {
      node.textContent = text.replace(reg, cur)
    })
  }

  /**
   * @param {Object} vue vue实例
   * @param {String} path 
   */
  getValInVueInstance (vue, path) {
    // path, for example: `b.m.n`
    let collector = vue
    path.split('.').forEach(seg => {
      if (!collector) {
        throw new Error('在你给的数据中找不到模板字符对应的值')
      }
      collector = collector[seg]
    })
    return collector
  }

  /**
   * @param {Object} vue 
   * @param {String} path 
   * @param {Object} value 
   */
  setValInVueInstance (vue, path, value) {
    let pointer = vue
    path = path.split('.')
    path.forEach((k, i) => {
      if (i < path.length - 1) {
        pointer = pointer[k]
      } else {
        pointer[k] = value
      }
    })
  }
}
